﻿
// MFCFFMPEGDlg.cpp: 实现文件
//

#include "pch.h"
#include "framework.h"
#include "MFCFFMPEG.h"
#include "MFCFFMPEGDlg.h"
#include "RTMPPushDlg.h"
#include "OnvifInfoDialog.h"
#include "GB28181Dialog.h"

#include "afxdialogex.h"
#include <iostream>

#ifdef _DEBUG
#define new DEBUG_NEW
#endif

extern "C"
{
#include "libavformat/avformat.h"
#include "libavutil/mathematics.h"
#include "libavutil/imgutils.h"
#include "libavutil/time.h"
#include "libswscale/swscale.h"
#include "libavutil/pixfmt.h"
#include "libavutil/pixdesc.h"
}

#pragma comment(lib, "avcodec.lib") 
#pragma comment(lib, "avdevice.lib") 
#pragma comment(lib, "avfilter.lib") 
#pragma comment(lib, "avformat.lib") 
#pragma comment(lib, "avutil.lib") 
//#pragma comment(lib, "postproc.lib") 
#pragma comment(lib, "swresample.lib") 
#pragma comment(lib, "swscale.lib") 

#pragma comment(lib, "ws2_32.lib")

#pragma warning(disable : 4996)

#define WM_USER_THREADEND WM_USER + 1

// 用于应用程序“关于”菜单项的 CAboutDlg 对话框

class CAboutDlg : public CDialogEx
{
public:
	CAboutDlg();

	// 对话框数据
#ifdef AFX_DESIGN_TIME
	enum { IDD = IDD_ABOUTBOX };
#endif

protected:
	virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV 支持

// 实现
protected:
	DECLARE_MESSAGE_MAP()
};

CAboutDlg::CAboutDlg() : CDialogEx(IDD_ABOUTBOX)
{
}

void CAboutDlg::DoDataExchange(CDataExchange* pDX)
{
	CDialogEx::DoDataExchange(pDX);
}

BEGIN_MESSAGE_MAP(CAboutDlg, CDialogEx)
END_MESSAGE_MAP()


// CMFCFFMPEGDlg 对话框



CMFCFFMPEGDlg::CMFCFFMPEGDlg(CWnd* pParent /*=nullptr*/)
	: CDialogEx(IDD_MFCFFMPEG_DIALOG, pParent)
	, m_SaveFileName(_T(""))
{
	m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
	m_threadLoop = 0;
}


void CMFCFFMPEGDlg::DoDataExchange(CDataExchange* pDX)
{
	CDialogEx::DoDataExchange(pDX);

	DDX_Control(pDX, IDC_CUSTOM1, m_yuvDraw);
	DDX_Text(pDX, IDC_EDIT_SAVENAME, m_SaveFileName);
}

BEGIN_MESSAGE_MAP(CMFCFFMPEGDlg, CDialogEx)
	ON_WM_SYSCOMMAND()
	ON_WM_PAINT()
	ON_WM_QUERYDRAGICON()
	ON_BN_CLICKED(IDC_BUTTON_PLAY, &CMFCFFMPEGDlg::OnBnClickedButtonPlay)
	ON_MESSAGE(WM_USER_THREADEND, OnUserThreadend)
	ON_COMMAND(ID_MENU_RTMPPUSH, &CMFCFFMPEGDlg::OnMenuRtmppush)
	ON_COMMAND(ID_MENU_ONVIF_INFO, &CMFCFFMPEGDlg::OnMenuOnvifInfo)
	ON_COMMAND(ID_MENU_GB28181, &CMFCFFMPEGDlg::OnMenuGb28181)
END_MESSAGE_MAP()


// CMFCFFMPEGDlg 消息处理程序

BOOL CMFCFFMPEGDlg::OnInitDialog()
{
	CDialogEx::OnInitDialog();

	// 将“关于...”菜单项添加到系统菜单中。

	// IDM_ABOUTBOX 必须在系统命令范围内。
	ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
	ASSERT(IDM_ABOUTBOX < 0xF000);

	CMenu* pSysMenu = GetSystemMenu(FALSE);
	if (pSysMenu != nullptr)
	{
		BOOL bNameValid;
		CString strAboutMenu;
		bNameValid = strAboutMenu.LoadString(IDS_ABOUTBOX);
		ASSERT(bNameValid);
		if (!strAboutMenu.IsEmpty())
		{
			pSysMenu->AppendMenu(MF_SEPARATOR);
			pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
		}
	}

	// 设置此对话框的图标。  当应用程序主窗口不是对话框时，框架将自动
	//  执行此操作
	SetIcon(m_hIcon, TRUE);			// 设置大图标
	SetIcon(m_hIcon, FALSE);		// 设置小图标

	SetDlgItemText(IDC_EDIT_URL, L"rtsp://192.168.0.150:554/cam/realmonitor?channel=1&subtype=0&unicast=true&proto=Onvif");

	SetDlgItemText(IDC_EDIT_URL, L"rtsp://wowzaec2demo.streamlock.net/vod/mp4:BigBuckBunny_115k.mov");

	SetDlgItemText(IDC_EDIT_SAVENAME, L"save.264");

	// TODO: 在此添加额外的初始化代码
	//::SetWindowPos(GetSafeHwnd(), HWND_TOPMOST, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE);
	//ShowWindow(SW_SHOW);

	return TRUE;  // 除非将焦点设置到控件，否则返回 TRUE
}

void CMFCFFMPEGDlg::OnSysCommand(UINT nID, LPARAM lParam)
{
	if ((nID & 0xFFF0) == IDM_ABOUTBOX)
	{
		CAboutDlg dlgAbout;
		dlgAbout.DoModal();
	}
	else
	{
		CDialogEx::OnSysCommand(nID, lParam);
	}
}

// 如果向对话框添加最小化按钮，则需要下面的代码
//  来绘制该图标。  对于使用文档/视图模型的 MFC 应用程序，
//  这将由框架自动完成。

void CMFCFFMPEGDlg::OnPaint()
{
	if (IsIconic())
	{
		CPaintDC dc(this); // 用于绘制的设备上下文

		SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);

		// 使图标在工作区矩形中居中
		int cxIcon = GetSystemMetrics(SM_CXICON);
		int cyIcon = GetSystemMetrics(SM_CYICON);
		CRect rect;
		GetClientRect(&rect);
		int x = (rect.Width() - cxIcon + 1) / 2;
		int y = (rect.Height() - cyIcon + 1) / 2;

		// 绘制图标
		dc.DrawIcon(x, y, m_hIcon);
	}
	else
	{
		CDialogEx::OnPaint();
	}
}

//当用户拖动最小化窗口时系统调用此函数取得光标
//显示。
HCURSOR CMFCFFMPEGDlg::OnQueryDragIcon()
{
	return static_cast<HCURSOR>(m_hIcon);
}

LRESULT CMFCFFMPEGDlg::OnUserThreadend(WPARAM wParam, LPARAM lParam)
{
	if (wParam == 0)
		putLog(L"下一帧");
	if (wParam == 0x01)
		putLog(L"停止");

	return 0;
}

void CMFCFFMPEGDlg::putLog(CString str)
{
	CTime tm;
	tm = CTime::GetCurrentTime();//获取系统日期
	str = tm.Format("%Y年%m月%d日 %X:") + str;

	SetDlgItemText(IDC_STATIC_STATUS, str);
}

void CMFCFFMPEGDlg::showStreamInfo(CString infoStr)
{
	SetDlgItemText(IDC_STATIC_INFO, infoStr);
}

void CMFCFFMPEGDlg::putRGB24Data(void* data, int width, int height)
{
#if 1
	int bpp = 24;

	BITMAPFILEHEADER bmpheader;
	BITMAPINFOHEADER bmpinfo;

	bmpheader.bfType = 0x4d42;
	bmpheader.bfReserved1 = 0;
	bmpheader.bfReserved2 = 0;
	bmpheader.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
	bmpheader.bfSize = bmpheader.bfOffBits + width * height * bpp / 8;

	bmpinfo.biSize = sizeof(BITMAPINFOHEADER);
	bmpinfo.biWidth = width;
	bmpinfo.biHeight = height;
	bmpinfo.biPlanes = 1;
	bmpinfo.biBitCount = bpp;
	bmpinfo.biCompression = BI_RGB;
	bmpinfo.biSizeImage = (width * bpp + 31) / 32 * 4 * height;
	bmpinfo.biXPelsPerMeter = 100;
	bmpinfo.biYPelsPerMeter = 100;
	bmpinfo.biClrUsed = 0;
	bmpinfo.biClrImportant = 0;

	CClientDC dc(NULL);

	HBITMAP m_hbitmap=CreateDIBitmap(dc.GetSafeHdc(),							//设备上下文的句柄 
		(LPBITMAPINFOHEADER)&bmpinfo,				//位图信息头指针 
		(long)CBM_INIT,								//初始化标志 
		data,						//初始化数据指针 
		(LPBITMAPINFO)&bmpinfo,						//位图信息指针 
		DIB_RGB_COLORS);
#else

	HDC hDC = ::GetDC(0);
	BITMAPINFOHEADER bmih;
	memset(&bmih, 0, sizeof(BITMAPINFOHEADER));
	bmih.biSize = sizeof(BITMAPINFOHEADER);
	bmih.biWidth = width;
	bmih.biHeight = height;
	bmih.biSizeImage = width * height;
	bmih.biCompression = BI_RGB;
	bmih.biBitCount = 24;
	bmih.biPlanes = 1;
	BYTE* pBits = (BYTE*)data;

	HBITMAP hBmp = CreateDIBSection(hDC, (BITMAPINFO*)&bmih, DIB_RGB_COLORS, (void**)&pBits, NULL, 0);
#endif
	m_yuvDraw.showBitmap(m_hbitmap, width, height);
}

int ffmpeg_SwsScale(AVFrame* srcFrame, AVFrame* dscFrame)
{
	struct SwsContext* m_pSwsContext;
	int ret = 0;
	m_pSwsContext = sws_getContext(srcFrame->width, srcFrame->height,
		(enum AVPixelFormat)srcFrame->format, dscFrame->width,
		dscFrame->height, (enum AVPixelFormat)dscFrame->format, SWS_BICUBIC, NULL, NULL,
		NULL);
	ret = sws_scale(m_pSwsContext, srcFrame->data, srcFrame->linesize, 0,
		srcFrame->height, dscFrame->data, dscFrame->linesize);
	sws_freeContext(m_pSwsContext);
	return ret;
}

static DWORD WINAPI PlayThread(LPVOID lpParam);

//开始播放
void CMFCFFMPEGDlg::OnBnClickedButtonPlay()
{
	GetDlgItem(IDC_EDIT_URL)->GetWindowText(m_url);
	UpdateData(true);//用于将屏幕上控件中的数据交换到变量中。
	//UpdateData(false);//用于将数据在屏幕中对应控件中显示出来。

	if (m_threadLoop == 1) {
		m_threadLoop = 0;
		SetDlgItemText(IDC_BUTTON_PLAY, L"开始播放");
	}
	else 
	{
		HANDLE hThread = CreateThread(NULL, 0, PlayThread, this, 0, NULL);
		m_threadLoop = 1;
		CloseHandle(hThread);

		SetDlgItemText(IDC_BUTTON_PLAY, L"停止播放");
	}
}

void CMFCFFMPEGDlg::OnMenuRtmppush()
{
	RTMPPushDlg *dlg = new RTMPPushDlg();
	dlg->Create(IDD_DIALOG_RTMPPUSH, GetDesktopWindow());
	dlg->ShowWindow(SW_SHOW);
}

void CMFCFFMPEGDlg::OnMenuOnvifInfo()
{
	// TODO: Onvif信息获取
	OnvifInfoDialog* dlg = new OnvifInfoDialog();
	dlg->Create(IDD_DIALOG_ONVIF_INFO, GetDesktopWindow());
	dlg->ShowWindow(SW_SHOW);
}


static DWORD WINAPI PlayThread(LPVOID lpParam)
{
	CMFCFFMPEGDlg* dlg = (CMFCFFMPEGDlg*)lpParam;
	CString str = L"开始播放";
	str += dlg->m_url;

	int dst_width;
	int dst_height;

	CRect wndRect1;
	dlg->m_yuvDraw.GetClientRect(wndRect1);
	dst_width = wndRect1.Width();
	dst_height = wndRect1.Height();

	dlg->putLog(str);

	CFile file;
	BOOL writeflag = file.Open(dlg->m_SaveFileName, CFile::modeCreate | CFile::modeWrite | CFile::typeBinary);

	char url[300];
	memset(url, 0, sizeof(url));

	int n = dlg->m_url.GetLength(); //按字符计算，str的长度
	int len = WideCharToMultiByte(CP_ACP, 0, dlg->m_url, n, NULL, 0, NULL, NULL);//按Byte计算str长度
	WideCharToMultiByte(CP_ACP, 0, dlg->m_url, n, url, len, NULL, NULL);//宽字节转换为多字节编码


	AVCodecContext* pCodecCtx = NULL;
	AVCodec* decoder = NULL;

	AVFormatContext* fmt_ctx = NULL;
	AVDictionary* options = NULL;

	AVFrame* frame = av_frame_alloc();

	int ret;
	int video_stream;
	av_dict_set(&options, "buffer_size", "1044000", 0);
	av_dict_set(&options, "max_delay", "500000", 0);
	av_dict_set(&options, "stimeout", "20000000", 0); //设置超时20秒
	av_dict_set(&options, "rtsp_transport", "tcp", 0);

	ret = avformat_open_input(&fmt_ctx, url, NULL, &options);
	if (ret < 0)
	{
		fprintf(stderr, "Could not open input\n");
		goto end;
	}

	ret = avformat_find_stream_info(fmt_ctx, NULL);
	if (ret < 0)
	{
		fprintf(stderr, "Could not find stream information\n");
		goto end;
	}

	ret = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
	if (ret < 0)
	{
		fprintf(stderr, "Cannot find a video stream in the input file\n");
		goto end;
	}
	video_stream = ret;
	av_dump_format(fmt_ctx, 0, url, 0);

	AVCodec* pCodec = NULL;

#ifdef RK3399
	if (AV_CODEC_ID_H264 == fmt_ctx->streams[video_stream]->codecpar->codec_id)
	{
		pCodec = avcodec_find_decoder_by_name("h264_rkmpp");
	}
	else if (AV_CODEC_ID_HEVC == fmt_ctx->streams[video_stream]->codecpar->codec_id)
	{
		pCodec = avcodec_find_decoder_by_name("hevc_rkmpp");
	}
#endif

	if (pCodec == NULL)
		pCodec = avcodec_find_decoder(fmt_ctx->streams[video_stream]->codecpar->codec_id);

	if (pCodec != NULL)
	{
		char dename[100];
		int width;
		int height;
		int fps;
		strcpy(dename, pCodec->name);

		width = fmt_ctx->streams[video_stream]->codecpar->width;
		height = fmt_ctx->streams[video_stream]->codecpar->height;
		fps = (int)av_q2d(fmt_ctx->streams[video_stream]->avg_frame_rate);

		CString infoStr;

		WCHAR szwText[MAX_PATH] = { '\0' };
		MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, dename, -1, szwText, MAX_PATH);

		infoStr.Format(L"decode[%s] %dx%d %d fps", szwText, width, height, fps);
		dlg->showStreamInfo(infoStr);
	}

	pCodecCtx = avcodec_alloc_context3(pCodec);
	avcodec_parameters_to_context(pCodecCtx, fmt_ctx->streams[video_stream]->codecpar);
	ret = avcodec_open2(pCodecCtx, pCodec, NULL);
	if (ret < 0)
	{
		fprintf(stderr, "Cannot open decode\n");
		goto end;
	}

	size_t unused = 0;
#ifdef RK3399
	{
		RKMPPDecodeContext* rk_context = pCodecCtx->priv_data;
		RKMPPDecoder* decoder = (RKMPPDecoder*)rk_context->decoder_ref->data;
		ret = mpp_buffer_group_limit_config(decoder->frame_group, 0, 30);
		unused = mpp_buffer_group_unused(decoder->frame_group);
		printf("mpp group limit: %d\n", unused);
	}
#endif

	AVPacket packet;

	while (dlg->m_threadLoop)
	{
		if ((ret = av_read_frame(fmt_ctx, &packet)) < 0)
			break;
		if (writeflag)
			file.Write(packet.data, packet.size);

		if (video_stream == packet.stream_index)
		{
			//if (stream->bk_raw_frame)
			//	stream->bk_raw_frame(stream, packet.data, packet.size);

			ret = avcodec_send_packet(pCodecCtx, &packet);
			if (ret < 0)
			{
				av_packet_unref(&packet);

				avcodec_flush_buffers(pCodecCtx);
				continue;
			}

			while (ret >= 0)
			{
				ret = avcodec_receive_frame(pCodecCtx, frame);
				if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
					break;
				else if (ret < 0)
				{
					break;
				}

				//YUV2RGB
				AVFrame* dstframe = av_frame_alloc();
				dstframe->format = AV_PIX_FMT_RGB24;
				dstframe->width = frame->width;
				dstframe->height = frame->height;

				int len = av_image_get_buffer_size(AV_PIX_FMT_RGB24, dstframe->width, dstframe->height, 1);
				uint8_t* buff = (uint8_t*)av_malloc(len);

				av_image_fill_arrays(dstframe->data,
					dstframe->linesize,
					buff, AV_PIX_FMT_RGB24,
					dstframe->width,
					dstframe->height,
					1);

				int v = ffmpeg_SwsScale(frame, dstframe);
				printf("scale:%d\n", v);

				dlg->putRGB24Data(buff, dstframe->width, dstframe->height);

				av_frame_free(&dstframe);
				av_free(buff);
				Sleep(10);
				::PostMessage(dlg->GetSafeHwnd(), WM_USER_THREADEND, 0, 0);
				//if (stream->bk_frame)
				//	stream->bk_frame(stream, frame);
			}

			if (AV_PKT_FLAG_KEY == packet.flags)
			{
			}
		}
		av_packet_unref(&packet);
	}
	/* flush the decoder */
	packet.data = NULL;
	packet.size = 0;
	av_packet_unref(&packet);

	avcodec_flush_buffers(pCodecCtx);
#ifdef RK3399
	{
		RKMPPDecodeContext* rk_context = pCodecCtx->priv_data;
		RKMPPDecoder* decoder = (RKMPPDecoder*)rk_context->decoder_ref->data;
		while (30 != (unused = mpp_buffer_group_unused(decoder->frame_group)))
		{
			sleep(1);
		}
	}
#endif

	avcodec_close(pCodecCtx);  //close,如果为rk3399的硬件编解码,则需要等待MPP_Buff释放完成后再关闭?是否需要这样不知道

end:
	av_frame_free(&frame);

	if (writeflag)
		file.Close();

	avformat_close_input(&fmt_ctx);
	avcodec_free_context(&pCodecCtx);

	dlg->m_threadLoop = 0;
	::PostMessage(dlg->m_hWnd, WM_USER_THREADEND, 0x01, 0);
	return 0;
}

void CMFCFFMPEGDlg::OnMenuGb28181()
{
	//MessageBox(L"GB28181");
	GB28181Dialog dlg;
	dlg.DoModal();
}
